package com.yuehua.learn.server.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.yuehua.learn.server.annotation.Decrypt;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartRequest;
import org.springframework.web.multipart.support.MultipartResolutionDelegate;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

/**
 * @author chsmWork
 * @version v1.0
 * @since 2022/8/25 13:41
 */
public class DecryptHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

    private static final String key = "324567876~#%$@%&";
    private static final String iv = "43571-1266053654";

    public static void main(String[] args) throws Exception {
        System.out.println(AES.encryptAES("hello", key, iv));
        System.out.println(AES.encryptAES("helloVal", key, iv));
        System.out.println(AES.encryptAES("{\n" +
                "    \"param\": \"helloPost\"\n" +
                "}", key, iv));
    }

    private static ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        Boolean annotation = parameter.getContainingClass().isAnnotationPresent(Decrypt.class);
        Boolean methodAnnotation = parameter.hasMethodAnnotation(Decrypt.class);
        boolean parameterAnnotation = parameter.hasParameterAnnotation(Decrypt.class);
        return annotation || methodAnnotation || parameterAnnotation;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest nativeRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        String method = nativeRequest.getMethod();
        if (method.equals("POST")) {
            Class<?> postClass = getPostClass(parameter);
            ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
            InputStream inputStream = StreamUtils.nonClosing(inputMessage.getBody());
            String str = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
            String decryptAES = AES.decryptAES(str, key, iv);
            return objectMapper.readValue(decryptAES, postClass);
        }
        if (!parameter.hasParameterAnnotation(PathVariable.class)) {
            return paramHandler(parameter, webRequest);
        }
        return pathVariableHandler(parameter, webRequest);
    }

    private ServletServerHttpRequest createInputMessage(NativeWebRequest webRequest) {
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        Assert.state(servletRequest != null, "No HttpServletRequest");
        return new ServletServerHttpRequest(servletRequest);
    }

    /**
     * 获取post需要转换的类型
     * @param parameter parameter
     * @return 参数类型
     */
    private Class<?> getPostClass(MethodParameter parameter) {
        Type targetType = parameter.getNestedGenericParameterType();
        Class<?> targetClass = (targetType instanceof Class ? (Class<?>) targetType : null);
        if (targetClass == null) {
            ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
            targetClass = resolvableType.resolve();
        }
        return targetClass;
    }

    /**
     * pathVariable 参数处理
     * @param parameter parameter
     * @param request request
     * @return 处理后的参数
     * @throws Exception 异常
     */
    private Object pathVariableHandler(MethodParameter parameter, NativeWebRequest request) throws Exception {
        String name = getName(parameter);
        Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
                HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
        String arg = uriTemplateVars != null ? uriTemplateVars.get(name) : null;
        if (arg != null) {
            return AES.decryptAES(arg, key, iv);
        }
        return null;
    }

    private String getName(MethodParameter parameter) {
        String name = null;
        if (!parameter.hasParameterAnnotation(PathVariable.class)) {
            RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
            if (ann != null) {
                name = ann.name();
            }

        } else {
            PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
            if (ann != null) {
                name = ann.name();
            }
        }
        if (ObjectUtils.isEmpty(name)) {
            name = parameter.getParameterName();
            if (name == null) {
                throw new IllegalArgumentException(
                        "Name for argument of type [" + parameter.getNestedParameterType().getName() +
                                "] not specified, and parameter name information not found in class file either.");
            }
        }
        return name;
    }

    private Object paramHandler(MethodParameter parameter, NativeWebRequest request) throws Exception {
        String name = getName(parameter);
        HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

        if (servletRequest != null) {
            Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
            if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
                return mpArg;
            }
        }

        Object arg = null;
        MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
        if (multipartRequest != null) {
            List<MultipartFile> files = multipartRequest.getFiles(name);
            if (!files.isEmpty()) {
                arg = (files.size() == 1 ? files.get(0) : files);
            }
        }
        if (arg == null) {
            String[] paramValues = request.getParameterValues(name);
            if (paramValues != null) {
                arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
            }
        }
        if (arg != null) {
            arg = AES.decryptAES(arg.toString(), key, iv);
        }
        return arg;
    }
}
